package com.qfedu.edu.seckill.orderservice.service.impl;

import com.alibaba.fastjson.JSON;
import com.qfedu.edu.seckill.bo.CreateOrderBack;
import com.qfedu.edu.seckill.bo.OrderShow;
import com.qfedu.edu.seckill.bo.QueueMessage;
import com.qfedu.edu.seckill.constance.RabbitMQConstance;
import com.qfedu.edu.seckill.constance.RedisConstance;
import com.qfedu.edu.seckill.exception.BusinessException;
import com.qfedu.edu.seckill.manager.RedisManager;
import com.qfedu.edu.seckill.orderservice.service.IOrderService;
import com.qfedu.edu.seckill.orderservice.utils.OrderNumberUtils;
import com.qfedu.edu.seckill.result.ResponseCode;
import com.qfedu.edu.seckill.vo.CreateOrderVo;
import lombok.extern.slf4j.Slf4j;
import org.qfedu.edu.seckill.service.IJwtService;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author xiaobobo
 * @title: OrderService
 * @projectName cd-java-2404-seckill-parent
 * @description: 这个是订单的业务类
 * @date 2025/2/6  10:26
 */
@Service
@Slf4j
public class OrderService implements IOrderService {


    @Autowired
    private IJwtService jwtService;

    @Autowired
    private RedisManager redisManager;

    @Autowired
    private RabbitTemplate rabbitTemplate;


    @Override
    public CreateOrderBack createOrder(CreateOrderVo createOrderVo, HttpServletRequest request) throws UnsupportedEncodingException {
        //首先第一步：校验参数的合法性
        checkParam(createOrderVo);
        //封装订单对象
        OrderShow order = getOrder(createOrderVo, request);
        //从Redis中实现减库存(我们只记录减去了多少的库存)
        reduceStockByRedis(order);
        //将订单信息保存到redis中给前端订单详情的页面使用
        saveOrderInfoToRedis(order);
        //接下来就是将订单信息信息放到MQ中充分利用MQ的消费端限流来做流量的消峰
        //将订单信息发送到MQ
        sendOrderToMQ(order);

        CreateOrderBack createOrderBack = new CreateOrderBack();
        createOrderBack.setOrderNumber(order.getOrderNum());
        String userId = getUserId(request);
        createOrderBack.setUserId(userId);
        return createOrderBack;
    }

    @Override
    public OrderShow listOrderByOrderNumber(String orderNumber) {
        //判断这个订单号是否为空
        if(StringUtils.isEmpty(orderNumber)){
           throw new BusinessException(ResponseCode.PARAM_EXCEPTION);
        }
        //接下来我们就可以从Redis中去查询数据了
        String orderShowJson = redisManager.getString(orderNumber);
        return JSON.parseObject(orderShowJson, OrderShow.class);
    }

    /**
     * 将订单信息放到MQ中去
     * @param order
     */
    private void sendOrderToMQ(OrderShow order) throws UnsupportedEncodingException {
        //这里需要封装一个统一的对象到队列中去 防止业务失败 重试的问题
        QueueMessage queueMessage = new QueueMessage();
        //在这里生成消息唯一的标识
        queueMessage.setMessageId(UUID.randomUUID().toString());
        queueMessage.setQueueName(RabbitMQConstance.getOrderQueue());
        queueMessage.setType(1);
        queueMessage.setMessage(JSON.toJSONString(order));
        //将上述对象转换成JSON格式
        String message = JSON.toJSONString(queueMessage);
        //接下来发送消息到队列
        rabbitTemplate.send(RabbitMQConstance.getOrderQueue(),
                new Message(message.getBytes("UTF-8"),
                        new MessageProperties()));
        log.info("发送消息到队列是成功的....");
    }

    /**
     * 保存订单信息到Redis中
     * @param order
     */
    private void saveOrderInfoToRedis(OrderShow order) {
        //设计: 使用Redis中的String类型来完成
        // key=订单号   val=order的JSON格式
        String key=order.getOrderNum();
        String val= JSON.toJSONString(order);
        redisManager.addString(key,val,30, TimeUnit.DAYS);
    }

    /**
     * 去Redis中实现减库存（直接在MySQL中减库存的话那么系统会死(MySQL)）
     * @param order
     */
    private void reduceStockByRedis(OrderShow order) {
        //这里只是需要记录减库存的数量是多少就可以了
        //怎么设计呢? 使用Redis中的String类型来完成
        // key= reduce:stock:课程id  数量
        //构建key
        String key= RedisConstance.getReduceStockPrefix()+order.getClassId();
        //进行计数
        redisManager.incr(key);
    }


    /**
     * 获取订单对象
     * @param createOrderVo
     * @param req
     */
    private OrderShow getOrder(CreateOrderVo createOrderVo, HttpServletRequest req) {
        //第二步：找到用户id
        String userId = getUserId(req);
        //第三步：生成订单号....
        //订单号的生成
        String orderNumber = OrderNumberUtils.getOrderNumber();
        //第四步：向订单表中插入数据 ?  这里注意：前端有高并发的情况 那么这里如果直接向数据库添加数据的话 那么MySQL会死  所以这里不能直接向数据库添加数据?
        OrderShow order = new OrderShow();
        order.setOrderNum(orderNumber);
        order.setActualFee(createOrderVo.getSecKillPrice().longValue());
        order.setUserId(userId);
        order.setTotalFee(createOrderVo.getSecKillPrice().longValue());
        order.setInvoiceType(0);
        order.setStatus(0);
        order.setPrice(createOrderVo.getPrice());
        order.setClassId(createOrderVo.getClassId());
        order.setClassName(createOrderVo.getClassName());
        //这里需要去Redis中查询这个数据的图片
        order.setPhoto("");
        return order;
    }

    /**
     * 通过请求获取用户id
     * @param req
     * @return
     */
    private String getUserId(HttpServletRequest req) {
        String token = req.getHeader("token");
        return jwtService.getUserId(token);
    }


    /**
     * 校验参数的合法性
     *
     * @param createOrderVo
     */
    private void checkParam(CreateOrderVo createOrderVo) {
        if (null == createOrderVo || null == createOrderVo.getPrice() || null == createOrderVo.getSecKillPrice() || null == createOrderVo.getQuantity() || StringUtils.isEmpty(createOrderVo.getClassName()) || StringUtils.isEmpty(createOrderVo.getClassId())) {
            throw new BusinessException(ResponseCode.PARAM_EXCEPTION);
        }
    }

}
